import React, { useEffect } from 'react'
import { connect } from 'dva'
import { ConfigProvider } from 'antd'
import find from 'lodash/find'
import { getLocale } from 'umi-plugin-react/locale'
import memoizeOne from 'memoize-one'
import isEqual from 'lodash/isEqual'
import isPlainObject from 'lodash/isPlainObject'
import get from 'lodash/get'
import dynamic from 'umi/dynamic'
import enUS from 'antd/es/locale/en_US'
import zhCN from 'antd/es/locale/zh_CN'
import moment from 'moment'
import { Redirect } from 'umi'
import pathToRegexp from 'path-to-regexp'
import 'moment/locale/zh-cn'
import { forEach, isFunction } from 'lodash'
import { TAB_CLOSE_CALLBACKS } from './BasicLayout'
import { parseQuery, setItem } from '@/utils/utils'
import LoadingComponent from '@/components/PageLoading/index'
import { themeConfig } from '@/defaultSettings'
import allMenus from '@/menu'
import { routeTo } from '@/utils/router'

let locale = zhCN

window.addEventListener('beforeunload', () => {
  forEach(TAB_CLOSE_CALLBACKS, (cb) => {
    isFunction(cb) && cb()
  })
})

if (getLocale() === 'zh-CN') {
  locale = zhCN
  moment.locale('zh-cn')
}

if (getLocale() === 'en-US') {
  locale = enUS
  moment.locale('en-US')
}

const BasicLayout = dynamic({
  loader: () => import('./BasicLayout'),
  loading: LoadingComponent,
})

const UserLayout = dynamic({
  loader: () => import('./UserLayout'),
  loading: LoadingComponent,
})

const statusIntervalId = null // checkStatus轮询

/**
 * 以pathname从后端返回的菜单数组中提取当前页面的菜单信息,情况分3种
 * 1、pathname以iframe开头
 * 2、普通路由
 * 3、嵌套路由 如：/user/login; /iframe/2769963
 * @param {string} pathname 当前页面的pathname,
 * @param {object[]} all 后端返回的菜单数组
 * @param {object[]} route
 * @returns {Object|undefined}  如果是对象，则必须包含3个字段：{menuId,urlAddr,menuName,menuCode}
 */
function _findMenu(pathname, all, route) {
  let result
  // 1、形式如/iframe/2769963的表示都是第三方提供的内嵌页面，用menuId查找
  if (/^\/iframe\/\w+/i.test(pathname)) {
    const menuCode = pathname.match(/^\/iframe\/(\w+)/)[1]
    result = find(all, { menuCode })
  }
  else {
    const menu = find(all, { urlAddr: pathname })
    if (isPlainObject(menu)) {
      // 2、普通路由
      result = menu
    }
    else if (menu === undefined) {
      // 3、嵌套路由
      const parent = find(all, (item) => {
        const { urlAddr } = item
        // 有的菜单没有配置urlAddr
        if (urlAddr === undefined)
          return false

        const rule = urlAddr.endsWith('/') ? `${urlAddr}:id?` : `${urlAddr}/:id?`
        // 不区分大小写  pathToRegexp('/notice/:id?').test('/notice/123')  => true
        return pathToRegexp(rule).test(pathname)
      })
      if (parent === undefined) {
        // 嵌套路由 不匹配
        result = undefined
      }
      else if (isPlainObject(parent)) {
        const component = find(get(route, 'routes') || [], (item) => {
          // 匹配 /notice/123 这样的动态路由
          const { path } = item

          // 有的菜单没有配置urlAddr
          if (path === undefined)
            return false

          const rule = path.endsWith('/') ? `${path}:id?` : `${path}/:id?`
          //
          return pathToRegexp(rule).test(pathname)
        })
        // 嵌套路由 匹配
        if (isPlainObject(component)) {
          const { menuId, urlAddr, menuName, menuCode } = parent
          result = { menuId, urlAddr, menuName, menuCode }
        }
        else {
          result = undefined
        }
      }
    }
  }
  return result
}

const findMenu = memoizeOne(_findMenu, isEqual)

function FrameLayout(props) {
  const {
    status,
    children,
    dispatch,
    initDataConfirmed,
    all,
    success,
    user,
    viewMode,
    ...rest
  } = props
  const {
    location: { pathname },
    route,
  } = rest

  const { isLogin = false, hasCheckedLogin = false } = user
  const ignoreLogin = viewMode === 'inner'
  const shouldLogin = !ignoreLogin && !isLogin

  /**
   * 这块的处理逻辑 需要兼顾noraml和inner两种视图模式
   *
   * ## normal模式
   * 1、未登录，登陆状态超时 或 访问/user/login
   *  - 使用 UserLayout模板
   * 2、已登录
   *  - 访问异常页面 则使用 Exception模板
   *  - 初始化失败：获取BasicLayout页面初始化数据失败，跳转到500页面
   *  - 初始化成功
   *    - 访问页面不存在 则使用 Exception模板(因为判断页面是否存在依赖于异步数据all，所以在异步结束前返回null，即让页面空白)
   *    - 其余使用 BasicLayout (进入basicLayout页面之前，需要保证初始化data异步获取成功)
   *
   * ## inner模式
   * 1. 未登录
   *  - 始总停留在loading界面，等待sessionId校验。
   * 2. 状态过期或直接访问登录页
   *  - redirect到403页面
   * 2. 已登录
   *  - 处理逻辑同normal模式下的已登录
   */
  function renderLayout() {
    // 免登陆
    if (ignoreLogin) {
      const currMenu = findMenu(pathname, allMenus, route)
      return (
        <BasicLayout
          currMenu={currMenu}
          {...rest}
        >
          {children}
        </BasicLayout>
      )
    }

    if (!hasCheckedLogin)
      return <LoadingComponent />

    // 未登录
    if (shouldLogin) {
      return pathname === '/user/login'
        ? (
        <UserLayout {...rest}>{children}</UserLayout>
          )
        : (
        <LoadingComponent />
          )
    }

    // 已登录，异步未发起或异步进行中
    if (initDataConfirmed === false)
      return <LoadingComponent />

    // 已登录，直接访问异常页面
    if (/^\/((404)|(500)|(403)\/?)$/.test(pathname)) {
      const Exception = dynamic({
        loader: () => import(`@/pages/Exception/${pathname.split('/')[1]}`),
        loading: LoadingComponent,
      })
      return <Exception />
    }

    // // 初始化成功：没找到当前页数据（首页是个特例 需要剔除）
    const currMenu = findMenu(pathname, all, route)
    if (pathname !== '/' && currMenu === undefined)
      return <Redirect to="/404" />

    // currMenu {object} 当前菜单信息 必须包含3个字段：{menuId,urlAddr,menuName}
    return (
      <BasicLayout
        {...rest}
        currMenu={
          pathname === '/' ? { menuName: '首页', urlAddr: '/', menuId: undefined } : currMenu
        }
      >
        {children}
      </BasicLayout>
    )
  }

  // 不是免登录，就检查登录状态
  useEffect(() => {
    if (!ignoreLogin) {
      dispatch({
        type: 'login/checkLogin',
      })
    }
  }, [dispatch])

  /**
   * 监听登录状态
   * 1、登录成功后： a、请求菜单数据；b、监听并记录用户最后操作时间；c、开始轮询，登录状态是否超时
   * 2、其他状态，停止以上a\b\c
   */
  useEffect(() => {
    if (isLogin) {
      dispatch({
        type: 'menu/getInitData',
      })
    }
  }, [dispatch, isLogin])

  // useEffect(() => {
  //   renderLayout(props);
  // });

  /**
   * 负责**未登录**时，页面的跳转。不论访问什么页面统一跳转到/user/login。
   * 并且需要带上'?redirtct=xxx'。
   * 比如:访问http://localhost:8000/#/bulletin
   * 需要变成'http://localhost:/user/login?redirtct=http://localhost:8000/#/bulletin'
   * 用于登录成功后跳转至指定页面。
   * 同时需要判断是否已带有'?redirtct=xxx' 因为登出时会自动在url上添加。避免重复
   */
  useEffect(() => {
    if (
      hasCheckedLogin
      && shouldLogin
      && pathname !== '/user/login'
    )
      routeTo('/user/login')
  }, [hasCheckedLogin, pathname, shouldLogin])

  useEffect(() => {
    const { bss3SessionId } = parseQuery(window.location.href)
    if (viewMode === 'inner' && bss3SessionId) {
      // TODO: 需要更换单点登录接口服务的地址，目前的服务是Mock的,所以需要先用user/login正常登录一边，再切换到inner模式时 否则在获取菜单数据时可能会出现500错误
      // dispatch({
      //   type: 'login/ssoLogin',
      //   payload: { bss3SessionId },
      // });
      setItem('bss3SessionId', bss3SessionId)
    }
  }, [viewMode, dispatch, status])

  return (
    <ConfigProvider locale={locale} prefixCls={themeConfig['@ant-prefix']}>
      {renderLayout()}
    </ConfigProvider>
  )
}

export default connect(({ login, menu, setting }) => ({
  status: login.status,
  viewMode: setting.viewMode,
  user: login,
  all: menu.all,
  success: menu.success,
  initDataConfirmed: menu.initDataConfirmed,
}))(FrameLayout)
